/**
 * 项目模型
 *
 * Created by snowingsea on 2020/02/06.
 */
const fs = require('fs-extra');
const mongoose = require('mongoose');
const moment = require('moment');
const escapeRegexp = require('escape-regexp');
const gitClient = require('./git/git-client');
const gitServer = require('./git/git-sever');
const Filter = require('./mongo/filter');
const Project = require('./mongo/project');
const projectVersions = require('./project-versions');
const tasks = require('./tasks');
const users = require('./users');
const publisher = require('./redis/publisher');
const common = require('./common');
const utility = require('./utility');
const { eventHub, eventKind } = require('./event-hub');

/**
 * 项目状态
 * @type {Object}
 */
const projectStatusKind = {
  deleting: -1, // 正在删除
  initial: 0, // 初始化状态
  normal: 1, // 正常状态
  frozen: 10, // 项目冻结
};

/**
 * 获取概要json信息
 * @param {Project} project 项目
 * @returns {Object}
 */
function getSummaryJson(project) {
  const projectId = project._id; // eslint-disable-line no-underscore-dangle
  const json = {
    projectId,
    projectName: project.projectName,
    createAt: projectId.getTimestamp().getTime(),
    visitAt: project.visitAt.getTime(),
    status: project.status,
    sharedUserGroup: project.sharedUsers || [],
  };
  if (project.status === projectStatusKind.frozen) {
    json.finishAt = project.finishAt.getTime();
  }
  if (project.lastVersion) {
    json.lastVersion = projectVersions.getSummaryJson(project.lastVersion);
  }
  return json;
}

/**
 * 获取项目json信息
 * @param {Project} project 项目信息
 * @return {Object}
 */
function getJson(project) {
  const json = getSummaryJson(project);
  json.description = project.description;
  json.repository = project.repository;
  return json;
}

/**
 * 获取项目键值
 * @param {String} creatorId 创建者id
 * @param {String} projectName 项目名
 * @return {String}
 */
function getProjectKey(creatorId, projectName) {
  return `${creatorId}:${projectName}`;
}

/**
 * 获取项目信息
 * TODO: 优化查询，联合查询
 * @param {String} projectId 项目id
 * @return {Promise}
 */
async function getProject(projectId) {
  const project = await Project.findOne({ _id: mongoose.Types.ObjectId(projectId) });
  if (!project) {
    return null;
  }
  if (project && project.lastVersionId) {
    const version = await projectVersions.getVersion(project.lastVersionId);
    project.lastVersion = version;
  }
  project.creator = await users.getUser(project.creatorId);
  let sharedUsers = [];
  if (project.sharedUserGroup) {
    sharedUsers = [...project.sharedUserGroup];
  }
  if (sharedUsers.length > 0) {
    const usersInfo = await users.getUsers({ userIds: sharedUsers });
    const usersMap = new Map();
    usersInfo.forEach((user) => {
      usersMap.set(user._id.toString(), user.username);
    });
    project.sharedUsers = sharedUsers.filter((userId) => usersMap.has(userId.toString())).map((userId) => ({
      id: userId.toString(),
      name: usersMap.get(userId.toString()),
    }));
  }
  return project;
}

/**
 * 获取项目集合
 * @param {Array} projectIds 项目id列表
 * @param {Array} exceptProjectIds 不计入的项目id列表
 * @param {Object} options 选项
 * @return {Promise}
 */
function getProjects({ projectIds = null, exceptProjectIds = null }, options = {}) {
  if (projectIds) {
    if (!utility.isArrayType(projectIds)) {
      projectIds = [projectIds];
    }
    projectIds = projectIds.map((projectId) => mongoose.Types.ObjectId(projectId));
    options._id = { $in: projectIds }; // eslint-disable-line no-underscore-dangle
  } else {
    if (!exceptProjectIds) {
      exceptProjectIds = [];
    }
    if (!utility.isArrayType(exceptProjectIds)) {
      exceptProjectIds = [exceptProjectIds];
    }
    exceptProjectIds = exceptProjectIds.map((projectId) => mongoose.Types.ObjectId(projectId));
    options._id = { $nin: exceptProjectIds }; // eslint-disable-line no-underscore-dangle
  }
  return Project.find(options);
}

/**
 * 获取项目列表请求约束
 * @param {Array|Number} status 项目状态
 * @param {String} q 搜索关键词
 * @return {Object}
 */
function getProjectOptions({ status = null, q = null }) {
  const options = {};
  // 项目状态
  if (status) {
    if (!utility.isArrayType(status)) {
      status = [status];
    }
    options.status = { $in: status };
  }
  // 关键词搜索
  if (q) {
    options.projectName = { $regex: `.*${escapeRegexp(q)}.*` };
  }
  return options;
}

/**
 * 筛选项目列表
 * @param {Object} pagination 分页信息
 * @param {Object} options 用户筛选项
 * @return {Promise}
 */
function filter(pagination = {}, options = {}) {
  return new Promise((resolve, reject) => {
    const { sortBy } = pagination;
    if (!sortBy || (sortBy === 'projectId') || (sortBy === 'createAt')) {
      pagination.sortBy = '_id';
    }
    let projects;
    Filter.filter(Project, pagination, options)
      .then((res) => {
        projects = res;
        const lastVersionIds = projects.map(({ lastVersionId }) => lastVersionId);
        return projectVersions.getVersions({ versionIds: lastVersionIds });
      })
      .then((versions) => {
        const lastVersions = new Map();
        versions.forEach((version) => {
          const versionId = version._id.toString(); // eslint-disable-line no-underscore-dangle
          lastVersions.set(versionId, version);
        });
        projects.forEach((project) => {
          if (project.lastVersionId) {
            project.lastVersion = lastVersions.get(project.lastVersionId.toString());
          }
        });
        resolve(projects);
      })
      .catch(reject);
  });
}

/**
 * 项目数目
 * @param {Object} options 用户筛选项
 * @return {Promise}
 */
function getCount(options = {}) {
  return Project.countDocuments(options);
}

/**
 * 更新项目信息
 * @param {String} projectId 项目id
 * @param {String} projectName 项目名
 * @param {Number} status 项目状态
 * @param {String} description 项目描述
 * @param {Project} project 项目
 * @param {User} creator 创建者
 * @return {Promise}
 */
function updateProject(
  projectId,
  {
    projectName = undefined,
    status = undefined,
    description = undefined,
    sharedUserGroup = undefined,
  },
  project = undefined,
  creator = undefined // eslint-disable-line comma-dangle
) {
  return new Promise((resolve, reject) => {
    const info = {};
    common.complementByProjectId(projectId, project, creator)
      .then(({ project }) => {
        if (projectName) {
          info.projectName = projectName;
          info.key = getProjectKey(project.creatorId, projectName);
        }
        if (status) {
          info.status = Number.parseInt(status.toString(), 10);
          if (info.status === projectStatusKind.frozen) {
            info.finishAt = moment();
          }
        }
        if (description || description === '') {
          info.description = description;
        }
        if (sharedUserGroup && utility.isArrayType(sharedUserGroup)) {
          info.sharedUserGroup = sharedUserGroup.map((id) => mongoose.Types.ObjectId(id));
        }
        return Project.updateOne({ _id: mongoose.Types.ObjectId(projectId) }, { $set: info });
      })
      .then(() => {
        if (projectName) {
          return tasks.updateProjectName(projectId, projectName);
        }
        return Promise.resolve();
      })
      .then((res) => {
        delete info.key;
        info.creatorId = project.creatorId;
        info.projectId = projectId;
        publisher.publishProjectMessage('update', info);
        resolve(res);
      })
      .catch(reject);
  });
}

/**
 * 删除项目
 * @param {String} projectId 项目id
 * @param {Project} project 项目
 * @param {User} creator 创建者
 * @return {Promise}
 */
function removeProject(projectId, project = undefined, creator = undefined) {
  return new Promise((resolve, reject) => {
    projectId = mongoose.Types.ObjectId(projectId);
    updateProject(projectId, { status: projectStatusKind.deleting }, project, creator)
      .then(() => common.complementByProjectId(projectId, project, creator))
      .then((res) => {
        project = res.project;
        creator = res.creator;
        return eventHub.emit(eventKind.EVENT_PROJECT_REMOVE, projectId);
      })
      .then(() => fs.remove(gitClient.getLocalRepoPath(creator, project)))
      .then(() => Project.deleteOne({ _id: projectId }))
      .then((res) => {
        const { creatorId } = project;
        publisher.publishProjectMessage('delete', { creatorId, projectId });
        resolve(res);
      })
      .catch(reject);
  });
}

/**
 * 添加项目
 * @param {String} creatorId 创建者id
 * @param {String} projectName 项目名
 * @param {String} language 项目类型
 * @param {String} projectType 编译器
 * @param {String} initVersion 初始版本
 * @param {String} description 项目描述
 * @param {User} creator 创建者
 * @param {License} license 证书
 * @param {Boolean} isEnviroment 是否是编译环境对应的项目
 * @return {Promise}
 */
function addProject(
  creatorId,
  projectName,
  language,
  projectType,
  initVersion = '_',
  description = '',
  creator = undefined,
  license = undefined,
  isEnviroment = false
) {
  return new Promise((resolve, reject) => {
    creatorId = mongoose.Types.ObjectId(creatorId);
    let projectId;
    let project = new Project({
      creatorId,
      projectName,
      description,
      visitAt: moment(),
      key: getProjectKey(creatorId, projectName),
    });
    project.save()
      .then((res) => {
        project = res;
        projectId = project._id;
        return common.complementByUserId(creatorId, creator);
      })
      .then(async (res) => {
        creator = res.user;
        const doc = { versionName: initVersion, language, projectType };
        if (isEnviroment) {
          return Project.updateOne({ _id: projectId }, { status: projectStatusKind.normal, $unset: { repository: 1 } });
        }
        await gitClient.initRepo(creator, project);
        const projectVersion = await projectVersions.addVersion(projectId, doc, project, creator, license);
        const lastVersionId = projectVersion._id;
        const { repository } = project;
        repository.url = gitServer.getRemoteURL(creator, project);
        return Project.updateOne({ _id: projectId }, {
          status: projectStatusKind.normal,
          lastVersionId,
          repository,
        });
      })
      .then(() => getProject(projectId))
      .then((project) => {
        const data = getJson(project);
        data.creatorId = project.creatorId;
        publisher.publishProjectMessage('add', data);
        resolve(project);
      })
      .catch((err) => {
        if (!projectId) {
          reject(err);
        } else {
          removeProject(projectId, project, creator).then(() => { reject(err); }).catch(reject);
        }
      });
  });
}

/**
 * 更新访问时间
 * @param {String} projectId 项目id
 * @return {Promise}
 */
function updateVisitTime(projectId) {
  return Project.updateOne({ _id: mongoose.Types.ObjectId(projectId) }, { visitAt: moment() });
}

// 所有用户最后访问项目的名称集合
async function namesSet(userId, {
  lastProjectOnly = false, // 只显示用户当前项目
} = {}) {
  const projectExec = Project.aggregate();
  if (userId) {
    projectExec.append({ $match: { creatorId: mongoose.Types.ObjectId(userId) } });
  }
  if (lastProjectOnly) {
    projectExec.append({
      $group: {
        _id: '$creatorId',
        projects: {
          $push: {
            creatorId: '$creatorId',
            _id: '$_id',
            projectName: '$projectName',
            lastVersionId: '$lastVersionId',
            visitAt: '$visitAt',
          },
        },
      },
    },
    {
      $replaceRoot: {
        newRoot: {
          // 只取最后访问项目
          $reduce: {
            input: '$projects',
            initialValue: null,
            in: {
              $cond: {
                if: { $gt: ['$$this.visitAt', '$$value.visitAt'] },
                then: '$$this',
                else: '$$value',
              },
            },
          },
        },
      },
    });
  }
  projectExec.append({
    $match: { projectName: { $ne: '' } },
  },
  {
    $group: {
      _id: null,
      names: {
        $addToSet: '$projectName',
      },
    },
  });
  const arr = await projectExec.exec();
  return arr.length ? arr[0].names.sort() : [];
}

eventHub.on(eventKind.EVENT_REMOVE_USER, async (userId) => {
  const projectsList = await getProjects({ exceptProjectIds: [] }, { creatorId: mongoose.Types.ObjectId(userId) });
  return Promise.all(projectsList.map((project) => removeProject(project._id, project)));
});

/**
 * Export model definition object.
 */
module.exports = {
  statusKind: projectStatusKind,

  getSummaryJson,
  getJson,

  getProject,
  getProjects,
  getProjectOptions,
  filter,
  getCount,

  addProject,
  removeProject,
  updateProject,
  updateVisitTime,
  namesSet,
};
